/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <queue>
#include <memory>
#include <map>
#include <baja/types.hpp>
#include <baja/trace.hpp>
#include <baja/mru_cache.hpp>
#include <journal/models/interfaces.h>

namespace journal { namespace model {

namespace b = baja;

typedef std::function<
    void(
        b::uint32,
        std::shared_ptr<ipage_model>
        )
    > page_mru_purge_function1;

class page_cache_manager
{
public:

    void run();
    void runAsync();

    void push(
        std::shared_ptr<ipage_model> page,
        page_level targetLevel);

    void clear();
    void stopAsync();

    page_cache_manager();
    ~page_cache_manager();

private:

    // private types

    enum priority_type
    {
        priority_next_command_0 = 7,
        priority_next_command_1 = 6,
        priority_next_command_2 = 5,
        priority_thumbnail = 4,
        priority_load_pass_0 = 3,
        priority_load_pass_1 = 2,
        priority_load_pass_2 = 1,
        priority_last = 0
    };

    enum command_state
    {
        command_not_started,
        command_started,
        command_cancelled
    };

    struct command_object
    {
        b::uint32 id;
        std::shared_ptr<ipage_model> page;
        command_state state;
        priority_type priority;
        page_level targetLevel;
        b::uint32 dependencyCount;
        b::uint32 dependentId;

        command_object();
        ~command_object();
        void dump(std::wstring prefix);
    };

    struct priority_command_pointer
    {
        std::shared_ptr<command_object> priorityObjectPointer;

        priority_command_pointer()
        {
        }

        priority_command_pointer(std::shared_ptr<command_object> objectPointer)
        {
            priorityObjectPointer = objectPointer;
        }

        bool operator <(const priority_command_pointer& r) const
        {
            return priorityObjectPointer->priority < r.priorityObjectPointer->priority;
        }
    };

    enum action_type
    {
        action_noop,
        action_load,
        action_delete
    };

    enum schedule_type
    {
        schedule_immediate,
        schedule_delayed_normal,
        schedule_delayed_high
    };

    enum load_passes
    {
        load_0,
        load_0_1_2
    };

    // private methods
    void scheduleCommand(
        std::shared_ptr<command_object> commandObject
        );

    void onCommandCompleted(
        priority_command_pointer command
        );

    void updateCommandsStates_SelfSelf(
        b::uint32 pageId,
        action_type& action,
        schedule_type& schedule,
        load_passes& passes,
        bool& stopLoading);

    void updateCommandsStates_SelfFull(
        b::uint32 pageId,
        action_type& action,
        schedule_type& schedule,
        load_passes& passes);

    void updateCommandsStates_FullFull(
        b::uint32 pageId,
        action_type& action,
        schedule_type& schedule,
        load_passes& passes);

    void updateCommandsStates_FullSelf(
        b::uint32 pageId,
        action_type& action,
        schedule_type& schedule,
        load_passes& passes);

    void updateCommandsStates_SelfThumbnail(
        b::uint32 pageId,
        action_type& action,
        schedule_type& schedule,
        load_passes& passes,
        bool& stopLoading);

    void updateCommandsStates_FullThumbnail(
        b::uint32 pageId,
        action_type& action,
        schedule_type& schedule,
        load_passes& passes);

    void updateCommandsStates_XDeleted(
        b::uint32 pageId,
        action_type& action,
        schedule_type& schedule,
        load_passes& passes);

    void scheduleFullLoad(
        std::shared_ptr<ipage_model> page,
        bool nextCommand);

    void executeTask(
        priority_command_pointer command,
        b::int32 threadIndex
        );

    void executeTaskAsync(
        priority_command_pointer command,
        b::int32 threadIndex
        );

    void onTaskCompleted(
        priority_command_pointer command,
        b::int32 threadIndex
        );

    void unloadPage(
        b::uint32 pageId,
        std::shared_ptr<ipage_model> page);

    void processQueued();
    void cancelAllQueued();

    bool page_cache_manager::pop(
        bool& readyFound,
        priority_command_pointer& outCommand);

    // private data members
    std::recursive_mutex _mutex;

    bool _loadBlocked;

    HANDLE _loadCompletedEvent;
    HANDLE _pushEvent;
    HANDLE _cancelAllEvent;
    HANDLE _exitLoadingEvent;

    const size_t _maxWorkerThreadCount;
    std::vector<HANDLE> _threadReady;

    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>> _pagesCommandsMap;
    std::priority_queue<priority_command_pointer> _commandsQueue;

    // unloading support...
    page_mru_purge_function1 _pageMRUPurgeFunction;
    b::mru_cache<b::uint32, std::shared_ptr<ipage_model>> _pagesMRU;
    b::uint32 _typeToken;
};


}} // namespace journal::model